/**
 * Copyright (C) 2011  Robin Dietrich, Nils Kenneweg, Leonard Stutz
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
 
#include "config.hpp"
#include "BaseApplication.hpp"
#include "chunkManager.hpp"
#include "textRenderer.hpp"

namespace minecraft {

	BaseApplication::BaseApplication(void)
		: mRoot(0),
		mCamera(0),
		mSceneMgr(0),
		mWindow(0),
		mResourcesCfg(std::string(P_ROOT)+"conf/ogre.cfg"),
		mPluginsCfg(Ogre::StringUtil::BLANK),
		mTrayMgr(0),
		mCameraMan(0),
		mDetailsPanel(0),
		mCursorWasVisible(false),
		mShutDown(false),
		mInputManager(0),
		mMouse(0),
		mKeyboard(0) {
        this->quit_state = false;
        this->cegui_state = false;
		this->game_started = false;
	}

	BaseApplication::~BaseApplication(void) {
		if (mTrayMgr) delete mTrayMgr;
		if (mCameraMan) delete mCameraMan;

		//Remove ourself as a Window listener
		Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, this);
		windowClosed(mWindow);
		delete mRoot;
	}

	bool BaseApplication::configure(void) {
		// Show the configuration dialog and initialise the system
		// You can skip this and use root.restoreConfig() to load configuration
		// settings if you were sure there are valid ones saved in ogre.cfg
		if(mRoot->showConfigDialog())
		{
			// If returned true, user clicked OK so initialise
			// Here we choose to let the system create a default rendering window by passing 'true'
			mWindow = mRoot->initialise(true, "TutorialApplication Render Window");

			return true;
		}
		else
		{
			return false;
		}
	}

	void BaseApplication::chooseSceneManager(void) {
		// Get the SceneManager, in this case a generic one
		mSceneMgr = mRoot->createSceneManager(Ogre::ST_GENERIC);
	}

	void BaseApplication::createCamera(void) {
		// Create the camera
		mCamera = mSceneMgr->createCamera("PlayerCam");
		//sChunkManager::Instance()->getBlock

		std::cout << "Creating player camera\n";
		
		mCamera->setPosition(Ogre::Vector3(0,0,0));
		// Look back along -Z
		mCamera->lookAt(Ogre::Vector3(0,0,-300));
		mCamera->setNearClipDistance(5);

		mCameraMan = new CameraMan(mCamera);   // create a default camera controller
		mCameraMan->setTopSpeed(5.0f);
	}

	void BaseApplication::createFrameListener(void) {
		Ogre::LogManager::getSingletonPtr()->logMessage("*** Initializing OIS ***");
		OIS::ParamList pl;
		size_t windowHnd = 0;
		std::ostringstream windowHndStr;

		mWindow->getCustomAttribute("WINDOW", &windowHnd);
		windowHndStr << windowHnd;
		pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

		mInputManager = OIS::InputManager::createInputSystem( pl );

		mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject( OIS::OISKeyboard, true ));
		mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject( OIS::OISMouse, true ));

		mMouse->setEventCallback(this);
		mKeyboard->setEventCallback(this);

		//Set initial mouse clipping size
		windowResized(mWindow);

		//Register as a Window listener
		Ogre::WindowEventUtilities::addWindowEventListener(mWindow, this);

		//mTrayMgr = new OgreBites::SdkTrayManager("InterfaceName", mWindow, mMouse, this);
		//mTrayMgr->showFrameStats(OgreBites::TL_BOTTOMLEFT);
		//mTrayMgr->showLogo(OgreBites::TL_BOTTOMRIGHT);
		//mTrayMgr->hideCursor();

		// create a params panel for displaying sample details
		//Ogre::StringVector items;
		//items.push_back("cam.pX");
		//items.push_back("cam.pY");
		//items.push_back("cam.pZ");
		//items.push_back("");
		//items.push_back("cam.oW");
		//items.push_back("cam.oX");
		//items.push_back("cam.oY");
		//items.push_back("cam.oZ");
		//items.push_back("");
		//items.push_back("Filtering");
		//items.push_back("Poly Mode");

		//mDetailsPanel = mTrayMgr->createParamsPanel(OgreBites::TL_NONE, "DetailsPanel", 200, items);
		//mDetailsPanel->setParamValue(9, "Bilinear");
		//mDetailsPanel->setParamValue(10, "Solid");
		//mDetailsPanel->hide();

		mRoot->addFrameListener(this);
	}

	void BaseApplication::destroyScene(void) {
	}

	void BaseApplication::createViewports(void) {
		// Create one viewport, entire window
		Ogre::Viewport* vp = mWindow->addViewport(mCamera);
		vp->setBackgroundColour(Ogre::ColourValue(0,0,0));

		// Alter the camera aspect ratio to match the viewport
		mCamera->setAspectRatio(
			Ogre::Real(vp->getActualWidth()) / Ogre::Real(vp->getActualHeight()));
	}

	void BaseApplication::setupResources(void) {
		// Load resource paths from config file
		Ogre::ConfigFile cf;
		cf.load(mResourcesCfg);

		// Go through all sections & settings in the file
		Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();

		Ogre::String secName, typeName, archName;
		while (seci.hasMoreElements())
		{
			secName = seci.peekNextKey();
			Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
			Ogre::ConfigFile::SettingsMultiMap::iterator i;
			for (i = settings->begin(); i != settings->end(); ++i)
			{
				typeName = i->first;
				archName = i->second;
				Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
					archName, typeName, secName);
			}
		}
	}

	void BaseApplication::createResourceListener(void) {

	}

	void BaseApplication::loadResources(void) {
		Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	}

	void BaseApplication::go(void) {
	#ifdef _DEBUG
		mResourcesCfg = std::string(P_ROOT)+"conf/resources_d.cfg";
		mPluginsCfg =  std::string(P_ROOT)+"conf/OgrePlugins_d.cfg";
	#else
		mResourcesCfg =  std::string(P_ROOT)+"conf/resources.cfg";
		mPluginsCfg =  std::string(P_ROOT)+"conf/OgrePlugins.cfg";
	#endif

		if (!setup())
			return;

		mRoot->startRendering();

		// clean up
		destroyScene();
	}

	bool BaseApplication::setup(void) {
		mRoot = new Ogre::Root(mPluginsCfg, std::string(P_ROOT)+"conf/ogre.cfg", std::string(P_ROOT)+"log/Ogre.log");

		setupResources();

		bool carryOn = configure();
		if (!carryOn) return false;

		chooseSceneManager();
		createCamera();
		createViewports();

		// Set default mipmap level (NB some APIs ignore this)
		Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(5);

		// Create any resource listeners (for loading screens)
		createResourceListener();
		// Load resources
		loadResources();
        
        // CEGUI setup
        mRenderer = &CEGUI::OgreRenderer::bootstrapSystem();
        CEGUI::Imageset::setDefaultResourceGroup("Imagesets");
        CEGUI::Font::setDefaultResourceGroup("Fonts");
        CEGUI::Scheme::setDefaultResourceGroup("Schemes");
        CEGUI::WidgetLookManager::setDefaultResourceGroup("LookNFeel");
        CEGUI::WindowManager::setDefaultResourceGroup("Layouts");
        
        CEGUI::SchemeManager::getSingleton().create("VanillaSkin.scheme");
        CEGUI::SchemeManager::getSingleton().create("TaharezLook.scheme");
        CEGUI::System::getSingleton().setDefaultMouseCursor("TaharezLook", "MouseArrow");

		// Create the scene
		createScene();

		createFrameListener();        

		return true;
	};

	bool BaseApplication::frameRenderingQueued(const Ogre::FrameEvent& evt) {
		if(mWindow->isClosed())
			return false;

		if(mShutDown)
			return false;

		//Need to capture/update each device
		mKeyboard->capture();
		mMouse->capture();
        
        //update camera que
        if (!this->cegui_state && game_started) {
            mCameraMan->frameRenderingQueued(evt);
        }
        
        //cegui frame indipentend updating
        CEGUI::System::getSingleton().injectTimePulse(evt.timeSinceLastFrame);
		

		return true;
	}
    
    CEGUI::MouseButton BaseApplication::convertButton(OIS::MouseButtonID buttonID) {
        switch (buttonID) {
            case OIS::MB_Left:
                return CEGUI::LeftButton;
            case OIS::MB_Right:
                return CEGUI::RightButton;
            case OIS::MB_Middle:
                return CEGUI::MiddleButton;
            default:
                return CEGUI::LeftButton;
        }
    }
    
    bool BaseApplication::quitButtonEvtY(const CEGUI::EventArgs &e) {
        this->mShutDown = true;
        return true;
    }
    
    bool BaseApplication::quitButtonEvtN(const CEGUI::EventArgs &e) {
        this->quit_state = false;
        this->cegui_state = false;
        CEGUI::MouseCursor::getSingleton().hide();
        CEGUI::WindowManager::getSingleton().destroyAllWindows();
        
        return true;
    }

	bool BaseApplication::keyPressed( const OIS::KeyEvent &arg ) {
		if (arg.key == OIS::KC_R)   // cycle polygon rendering mode
		{
			Ogre::String newVal;
			Ogre::PolygonMode pm;

			switch (mCamera->getPolygonMode())
			{
			case Ogre::PM_SOLID:
				newVal = "Wireframe";
				pm = Ogre::PM_WIREFRAME;
				break;
			case Ogre::PM_WIREFRAME:
				newVal = "Points";
				pm = Ogre::PM_POINTS;
				break;
			default:
				newVal = "Solid";
				pm = Ogre::PM_SOLID;
			}

			mCamera->setPolygonMode(pm);
			mDetailsPanel->setParamValue(10, newVal);
		}
		else if(arg.key == OIS::KC_F5)   // refresh all textures
		{
			Ogre::TextureManager::getSingleton().reloadAll();
		}
		else if (arg.key == OIS::KC_SYSRQ)   // take a screenshot
		{
			mWindow->writeContentsToTimestampedFile("screenshot", ".jpg");
		}
		else if (arg.key == OIS::KC_ESCAPE)
		{
            if (this->cegui_state && this->quit_state) {
                this->quit_state = false;
                this->cegui_state = false;
                CEGUI::MouseCursor::getSingleton().hide();
                CEGUI::WindowManager::getSingleton().destroyAllWindows();
            } else if (!this->quit_state && !this->cegui_state) {
                this->quit_state = true;
                this->cegui_state = true;
                CEGUI::MouseCursor::getSingleton().show();
                CEGUI::Window *exitWnd = CEGUI::WindowManager::getSingleton().loadWindowLayout("ExitWindow.layout");
                exitWnd->getChild("ExitWindow")->getChild("ExitWindow/QuitY")->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&BaseApplication::quitButtonEvtY, this));
                exitWnd->getChild("ExitWindow")->getChild("ExitWindow/QuitN")->subscribeEvent(CEGUI::PushButton::EventClicked, CEGUI::Event::Subscriber(&BaseApplication::quitButtonEvtN, this));

                CEGUI::System::getSingleton().setGUISheet(exitWnd);
            }
		}

        if (!this->cegui_state) {
            mCameraMan->injectKeyDown(arg);
        }
        CEGUI::System &sys = CEGUI::System::getSingleton();
        sys.injectKeyDown(arg.key);
        sys.injectChar(arg.text);
		return true;
	}

	bool BaseApplication::keyReleased( const OIS::KeyEvent &arg ) {
        if (!this->cegui_state) {
            mCameraMan->injectKeyUp(arg);
        }
        CEGUI::System::getSingleton().injectKeyUp(arg.key);
		return true;
	}

	bool BaseApplication::mouseReleased( const OIS::MouseEvent &arg, OIS::MouseButtonID id ) {
        if (!this->cegui_state) {
            mCameraMan->injectMouseUp(arg, id);
        }
        CEGUI::System::getSingleton().injectMouseButtonUp(convertButton(id));
		return true;
	}

	void BaseApplication::windowResized(Ogre::RenderWindow* rw) {
		//Adjust mouse clipping area
		unsigned int width, height, depth;
		int left, top;
		rw->getMetrics(width, height, depth, left, top);

		const OIS::MouseState &ms = mMouse->getMouseState();
		ms.width = width;
		ms.height = height;
	}

	void BaseApplication::windowClosed(Ogre::RenderWindow* rw) {
		//Unattach OIS before window shutdown (very important under Linux)
		//Only close for window that created OIS (the main window in these demos)
		if( rw == mWindow )
		{
			if( mInputManager )
			{
				mInputManager->destroyInputObject( mMouse );
				mInputManager->destroyInputObject( mKeyboard );

				OIS::InputManager::destroyInputSystem(mInputManager);
				mInputManager = 0;
			}
		}
	}
	
}
